package web

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"strings"
	"time"
)

var (
	AccessTokenKey  = []byte("kUD7HXe4bMG6sUWV8pEyQ6JxNQTZkYtu")
	RefreshTokenKey = []byte("pEyQ6JxNQTZk6sUXepEyQ6kUD7HXe4bM")
)

type IJwtHandler interface {
	SetJWTToken(ctx *gin.Context, userId int64, ssid string) error
	SetRefreshToken(ctx *gin.Context, userId int64, ssid string) error
	SetLoginToken(ctx *gin.Context, userId int64) error
	ExtractToken(ctx *gin.Context) (string, error)
	ClearToken(ctx *gin.Context) error
	CheckSsid(ctx *gin.Context, ssid string) (bool, error)
}

type JwtHandler struct {
	cmd redis.Cmdable
}

func NewJwtHandler(cmd redis.Cmdable) IJwtHandler {
	return &JwtHandler{
		cmd: cmd,
	}
}

func (h *JwtHandler) SetJWTToken(ctx *gin.Context, userId int64, ssid string) error {
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, UserClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 30)),
		},
		Uid:       userId,
		UserAgent: ctx.Request.UserAgent(),
		Ssid:      ssid,
	})
	tokenStr, err := token.SignedString(AccessTokenKey)
	if err != nil {
		return err
	}
	// 保存到请求头
	ctx.Header("x-jwt-token", tokenStr)
	return nil
}

func (h *JwtHandler) SetRefreshToken(ctx *gin.Context, userId int64, ssid string) error {
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, RefreshClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)),
		},
		Uid:       userId,
		UserAgent: ctx.Request.UserAgent(),
		Ssid:      ssid,
	})
	tokenStr, err := token.SignedString(RefreshTokenKey)
	if err != nil {
		return err
	}
	// 保存到请求头
	ctx.Header("x-refresh-token", tokenStr)
	return nil
}

func (h *JwtHandler) SetLoginToken(ctx *gin.Context, userId int64) error {
	// 生成ssid
	ssid := uuid.New().String()
	err := h.SetJWTToken(ctx, userId, ssid)
	if err != nil {
		return err
	}
	return h.SetRefreshToken(ctx, userId, ssid)
}

func (h *JwtHandler) ExtractToken(ctx *gin.Context) (string, error) {
	// 进行登录校验accessToken
	header := ctx.GetHeader("authorization")
	if len(header) == 0 {
		return "", errors.New("token 异常")
	}
	splitArr := strings.Split(header, " ")
	token := splitArr[1]
	if len(token) == 0 {
		return "", errors.New("token 异常")
	}
	return token, nil
}

func (h *JwtHandler) ClearToken(ctx *gin.Context) error {
	// 1. 移除x-jwt-token和x-refresh-token
	ctx.Header("x-jwt-token", "")
	ctx.Header("x-refresh-token", "")
	// 2. 往redis中记录移除的ssid
	ssid, ok := ctx.MustGet("ssid").(string)
	if !ok {
		return errors.New("移除token错误")
	}
	// 3. 设置redis该ssid不可用
	err := h.cmd.Set(ctx, "user:logout:ssid:"+ssid, "", time.Hour*24*7).Err()
	if err != nil {
		return err
	}
	return nil
}

func (h *JwtHandler) CheckSsid(ctx *gin.Context, ssid string) (bool, error) {
	// 校验是否已经退出登录
	result, err := h.cmd.Exists(ctx, "user:logout:ssid:"+ssid).Result()
	if err != nil {
		return false, err
	}
	if result == 0 {
		return false, nil
	}
	return true, nil
}

// UserClaims 自定义载荷
type UserClaims struct {
	jwt.RegisteredClaims
	Uid       int64
	UserAgent string
	Ssid      string
}

// RefreshClaims 自定义载荷
type RefreshClaims struct {
	jwt.RegisteredClaims
	Uid       int64
	UserAgent string
	Ssid      string
}
